/*
  ArtToSpr Artworks/Draw to Sprite convertor
  Copyright (c) 1998 Tony Houghton

  This source is distributed under the GPL. Please see the file
  "COPYING" for details.
*/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "colourtran.h"
#include "messagetra.h"
#include "os.h"
#include "osbyte.h"
#include "osfile.h"

#include "colourdbox.h"
#include "event.h"
#include "gadgets.h"
#include "menu.h"
#include "wimplib.h"
#include "window.h"

#include "err.h"
#include "gadgfade.h"
#include "msgtrans.h"
#include "opts.h"
#include "rescodes.h"

#define DEFAULT_MARGIN 8

#define OPTS_ID ('A' + ('T'<<8) + ('S'<<16) + ('O'<<24))
#define OPTS_VERSION 2

static const char choices_load[] = "Choices:ArtToSpr";
static const char choices_write[] = "<Choices$Write>.ArtToSpr";
static const char internal_choices[] = "<ArtToSpr$Dir>.Choices";

options default_opts;
ObjectId opts_default_id;

static bool risc_pc;

static int opts_filetype(const char *filename)
{
  fileswitch_object_type objtype;
  bits filetype;

  if (E(xosfile_read_stamped(filename, &objtype, 0, 0, 0, 0, &filetype)))
    return -1;
  return (objtype == 1) ? (int) filetype : -1;
}

static bool opts_load_binary(FILE *fp)
{
  int a[2];

  if (fread(&a, sizeof(int), 2, fp) != 2)
  {
    err_complain(0, msgs_qlookup("BadOpts"));
    return false;
  }
  if (a[0] != OPTS_ID || a[1] != OPTS_VERSION)
  {
    err_complain(0, msgs_qlookup("BadVer"));
    return false;
  }
  if (fread(&default_opts, sizeof(options) - opts_DONTSAVE, 1, fp) != 1)
  {
    err_complain(0, msgs_qlookup("BadOpts"));
    return false;
  }
  return true;
}

typedef enum {
  opts_OK,
  opts_Missing,
  opts_Invalid
} opts_lookup_result;

static opts_lookup_result opts_lookup_int(const MessagesFD *mfd,
                                          const char *tag,
                                          int *result, int min, int max)
{
  const char *str = msgtrans_lookup(mfd, tag);
  if (str)
  {
    *result = atoi(str);
    if (*result < min || *result > max)
    {
      char *badval = malloc(strlen(str) + 1);
      if (badval)
        strcpy(badval, str);
      err_complain(0, msgs_plookup("OptsSyntax", badval ? badval : "OOPS",
                   tag, 0, 0));
      free(badval);
      return opts_Invalid;
    }
    return opts_OK;
  }
  else
    return opts_Missing;
}

static opts_lookup_result opts_lookup_double(const MessagesFD *mfd,
                                             const char *tag,
                                             double *result,
                                             double min, double max)
{
  const char *str = msgtrans_lookup(mfd, tag);
  if (str)
  {
    *result = atof(str);
    if (*result < min || *result > max)
    {
      err_complain(0, msgs_plookup("OptsSyntax", str, tag, 0, 0));
      return opts_Invalid;
    }
    return opts_OK;
  }
  else
    return opts_Missing;
}

static opts_lookup_result opts_lookup_bool(const MessagesFD *mfd,
                                           const char *tag,
                                           bool *result)
{
  const char *str = msgtrans_lookup(mfd, tag);
  if (str)
  {
    switch (tolower(str[0]))
    {
      case '1': case 'y': case 't':
        *result = true;
        return opts_OK;
      case '0': case 'n': case 'f':
        *result = false;
        return opts_OK;
      default:
        err_complain(0, msgs_plookup("OptsSyntax", str, tag, 0, 0));
        return opts_Invalid;
    }
  }
  else
    return opts_Missing;
}

static opts_lookup_result opts_lookup_hex(const MessagesFD *mfd,
                                          const char *tag,
                                          unsigned long int *result,
                                          unsigned long int min,
                                          unsigned long int max)
{
  const char *str = msgtrans_lookup(mfd, tag);
  if (str)
  {
    switch (str[0])
    {
      case '#': case '&':
        ++str;
        break;
      case '0':
        if (tolower(str[1]) == 'x')
          str += 2;
        break;
    }
    *result = strtoul(str, 0, 16);
    if (*result < min || *result > max)
    {
      err_complain(0, msgs_plookup("OptsSyntax", str, tag, 0, 0));
      return opts_Invalid;
    }
    return opts_OK;
  }
  else
    return opts_Missing;
}

static bool opts_load_text(const MessagesFD *mfd)
{
  struct {
    unsigned long int ul;
    int i;
    double f;
    bool b;
  } result;
  bool valid = true;

  switch (opts_lookup_int(mfd, "ColourDepth", &result.i, 0, 5))
  {
    case opts_OK:
      default_opts.colours = result.i;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_int(mfd, "DPI", &result.i, 0, 4))
  {
    case opts_OK:
      default_opts.resolution = result.i;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_hex(mfd, "Background", &result.ul, 0, 0xffffff))
  {
    case opts_OK:
      default_opts.bg_colour = result.ul << 8;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_bool(mfd, "Mask", &result.b))
  {
    case opts_OK:
      default_opts.mask = result.b;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_int(mfd, "Quality", &result.i, 0, 3))
  {
    case opts_OK:
      default_opts.quality = result.i;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_int(mfd, "Margin", &result.i, -1000, 1000))
  {
    case opts_OK:
      default_opts.margin = result.i;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_double(mfd, "Rotation", &result.f, -360.0, 360.0))
  {
    case opts_OK:
      default_opts.angle = result.f;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_bool(mfd, "ScaleToFit", &result.b))
  {
    case opts_OK:
      default_opts.scale_to_fit = result.b;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (default_opts.scale_to_fit)
  {
    case true:
      switch (opts_lookup_int(mfd, "ScaleX", &result.i, 0, 3200))
      {
        case opts_OK:
          default_opts.scale_fit_x = result.i;
          break;
        case opts_Invalid:
          valid = false;
          break;
      }
      switch (opts_lookup_int(mfd, "ScaleY", &result.i, 0, 2400))
      {
        case opts_OK:
          default_opts.scale_fit_y = result.i;
          break;
        case opts_Invalid:
          valid = false;
          break;
      }
      break;
    case false:
      switch (opts_lookup_double(mfd, "ScaleX", &result.f, -10000.0, 10000.0))
      {
        case opts_OK:
          default_opts.scale_x = result.f;
          break;
        case opts_Invalid:
          valid = false;
          break;
      }
      switch (opts_lookup_double(mfd, "ScaleY", &result.f, -10000.0, 10000.0))
      {
        case opts_OK:
          default_opts.scale_y = result.f;
          break;
        case opts_Invalid:
          valid = false;
          break;
      }
      break;
  }
  switch (opts_lookup_bool(mfd, "PreserveAspect", &result.b))
  {
    case opts_OK:
      default_opts.keep_aspect = result.b;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  switch (opts_lookup_int(mfd, "Surplus", &result.i, 0, 2))
  {
    case opts_OK:
      default_opts.aspect = result.i + gadget_EDGE;
      break;
    case opts_Invalid:
      valid = false;
      break;
  }
  return true;
}

static bool opts_load(const char *filename)
{
  FILE *fp;
  bool result;
  int size;
  messagetrans_control_block mfd;
  char *buffer;

  switch (opts_filetype(filename))
  {
    case 0xffd:         /* Data */
      fp = fopen(filename, "rb");
      if (!fp)
        return false;
      result = opts_load_binary(fp);
      fclose(fp);
      return result;
    case 0xfff:         /* Text */
      if (E(xmessagetrans_file_info(filename, 0, &size)))
      {
        return false;
      }
      if ((buffer = malloc(size)) == 0)
      {
        return !E(msgs_nomem());
      }
      if (E(xmessagetrans_open_file(&mfd, filename, buffer)))
      {
        free(buffer);
        return false;
      }
      result = opts_load_text((MessagesFD *) &mfd);
      xmessagetrans_close_file(&mfd);
      free(buffer);
      return result;
    default:
      err_complain(0, "OptsType");
      return false;
  }
}

int opts_calculate_mode(int colours, int dpi)
{
  switch (colours)
  {
    case colours_2:
      switch (dpi)
      {
        case dpi_45x45:
          return 4;
        case dpi_90x45:
          return 0;
        case dpi_90x90:
          return 25;
        default:
          return -1;
      }
    case colours_4:
      switch (dpi)
      {
        case dpi_45x45:
          return 1;
        case dpi_90x45:
          return 8;
        case dpi_90x90:
          return 26;
        default:
          return -1;
      }
    case colours_16:
      switch (dpi)
      {
        case dpi_45x45:
          return 9;
        case dpi_90x45:
          return 12;
        case dpi_90x90:
          return 27;
        default:
          return -1;
      }
    case colours_256:
      switch (dpi)
      {
        case dpi_45x45:
          return 13;
        case dpi_90x45:
          return 15;
        case dpi_90x90:
          return 28;
        default:
          return -1;
      }
  }
  return -1;
}

static void update_mode(ObjectId win)
{
  int mode;
  char modestr[4];
  int colours, resolution;

  E(stringset_get_selected(StringSet_IndexedSelection, win, gadget_COLOURDEPTH,
  	&colours));
  E(stringset_get_selected(StringSet_IndexedSelection, win, gadget_RESOLUTION,
  	&resolution));
  mode = opts_calculate_mode(colours, resolution);
  if (mode == -1)
    modestr[0] = 0;
  else
    sprintf(modestr, "%d", mode);
  E(button_set_value(0, win, gadget_MODE, modestr));
}

static void redraw_colour(ObjectId win)
{
  BBox redraw;

  E(gadget_get_bbox(0, win, gadget_BGCOLOUR, &redraw));
  E(window_force_redraw(0, win, &redraw));
}

static void reflect_transparency(ObjectId win, bool transparent,
                                 unsigned long int bg_colour)
{
  switch (transparent)
  {
    case false:
      E(button_set_value(0, win, gadget_BGCOLOUR, ""));
      E(button_set_flags(0, win, gadget_BGCOLOUR, 0xf * WimpIcon_FGColour,
                         7 * WimpIcon_FGColour));
      break;
    case true:
      E(button_set_value(0, win, gadget_BGCOLOUR, "M"));
      /* Make sure foreground colour contrasts with background */
      E(button_set_flags(0, win, gadget_BGCOLOUR, 0xf * WimpIcon_FGColour,
                             (   (bg_colour >> 24)
                               + (bg_colour >> 16 & 0xff)
                               + (bg_colour >> 8 & 0xff)
                              > 3 * 0x80
                           ? 7 : 0)
                         * WimpIcon_FGColour));
      break;
  }
}

static void update_fades(ObjectId win)
{
  bool state;

  if (E(radiobutton_get_state(0, win, gadget_SCALE, &state, 0)))
    return;
  E(gadget_set_fade(win, gadget_SCALEX, !state));
  E(gadget_set_fade(win, gadget_SCALEY, !state));
  E(gadget_set_fade(win, gadget_SCALETOFITX, state));
  E(gadget_set_fade(win, gadget_SCALETOFITY, state));
  E(gadget_set_fade(win, gadget_KEEPASPECT, state));

  E(gadget_get_fade(win, gadget_KEEPASPECT, &state));
  state = !state;
  if (state)
    E(optionbutton_get_state(0, win, gadget_KEEPASPECT, &state));
  E(gadget_set_fade(win, gadget_EDGE, !state));
  E(gadget_set_fade(win, gadget_TRIM, !state));
  E(gadget_set_fade(win, gadget_CENTRE, !state));
}

void opts_update_mode(options const *opts, ObjectId win)
{
  E(stringset_set_selected(StringSet_IndexedSelection, win, gadget_COLOURDEPTH,
  	(char *) opts->colours));
  E(stringset_set_selected(StringSet_IndexedSelection, win, gadget_RESOLUTION,
  	(char *) opts->resolution));
  update_mode(win);
}

static void write_window(options *opts, ObjectId win)
{
  opts_update_mode(opts, win);
  E(stringset_set_selected(StringSet_IndexedSelection, win, gadget_QUALITY,
  	(char *) opts->quality));
  E(numberrange_set_value(0, win, gadget_MARGIN, opts->margin));
  E(numberrange_set_value(0, win, gadget_ANGLE, (int) (opts->angle * 10.0)));
  E(radiobutton_set_state(0, win, gadget_SCALETOFIT, opts->scale_to_fit));
  E(numberrange_set_value(0, win, gadget_SCALEX,
  	(int) (opts->scale_x * 1000.0)));
  E(numberrange_set_value(0, win, gadget_SCALEY,
  	(int) (opts->scale_y * 1000.0)));
  E(numberrange_set_value(0, win, gadget_SCALETOFITX, opts->scale_fit_x));
  E(numberrange_set_value(0, win, gadget_SCALETOFITY, opts->scale_fit_y));
  E(optionbutton_set_state(0, win, gadget_KEEPASPECT, opts->keep_aspect));
  E(radiobutton_set_state(0, win, opts->aspect, true));
  update_fades(win);
  opts->temp_mask = opts->mask;
  opts->temp_bg = opts->bg_colour;
  if (!risc_pc)
  {
    ObjectId pm;

    E(stringset_get_components(StringSet_GetComponents_ReturnPopUpMenu,
    	win, gadget_COLOURDEPTH, 0, &pm));
    E(menu_set_fade(0, pm, colours_32K, true));
    E(menu_set_fade(0, pm, colours_16M, true));
    E(stringset_get_components(StringSet_GetComponents_ReturnPopUpMenu,
    	win, gadget_RESOLUTION, 0, &pm));
    E(menu_set_fade(0, pm, dpi_22x22, true));
    E(menu_set_fade(0, pm, dpi_180x180, true));
  }
  redraw_colour(win);
  reflect_transparency(win, opts->temp_mask, opts->temp_bg);
}

static void read_window(options *opts, ObjectId win)
{
  int a;

  E(stringset_get_selected(StringSet_IndexedSelection, win, gadget_COLOURDEPTH,
  	&opts->colours));
  E(stringset_get_selected(StringSet_IndexedSelection, win, gadget_RESOLUTION,
  	&opts->resolution));
  E(stringset_get_selected(StringSet_IndexedSelection, win, gadget_QUALITY,
  	&opts->quality));
  E(numberrange_get_value(0, win, gadget_MARGIN, &opts->margin));
  E(numberrange_get_value(0, win, gadget_ANGLE, &a));
  opts->angle = (double) a / 10.0;
  E(radiobutton_get_state(0, win, gadget_SCALETOFIT, &opts->scale_to_fit, 0));
  E(numberrange_get_value(0, win, gadget_SCALEX, &a));
  opts->scale_x = (double) a / 1000.0;
  E(numberrange_get_value(0, win, gadget_SCALEY, &a));
  opts->scale_y = (double) a / 1000.0;
  E(numberrange_get_value(0, win, gadget_SCALETOFITX, &opts->scale_fit_x));
  E(numberrange_get_value(0, win, gadget_SCALETOFITY, &opts->scale_fit_y));
  E(optionbutton_get_state(0, win, gadget_KEEPASPECT, &opts->keep_aspect));
  E(radiobutton_get_state(0, win, gadget_EDGE, 0, &opts->aspect));
  opts->mask = opts->temp_mask;
  opts->bg_colour = opts->temp_bg;
}

static bool redraw_handler(int c, WimpPollBlock *e, IdBlock *id, void *h)
{
  BBox bounds;
  bool more;
  WimpRedrawWindowBlock redraw;

  c=c;

  gadget_get_bbox(0, id->self_id, gadget_BGCOLOUR, &bounds);

  redraw.window_handle = e->redraw_window_request.window_handle;
  wimp_redraw_window(&redraw, &more);
  while (more)
  {
    BBox war;

    war.xmin = redraw.redraw_area.xmin -
    	redraw.visible_area.xmin + redraw.xscroll;
    war.xmax = redraw.redraw_area.xmax -
    	redraw.visible_area.xmin + redraw.xscroll;
    war.ymin = redraw.redraw_area.ymin -
    	redraw.visible_area.ymax + redraw.yscroll;
    war.ymax = redraw.redraw_area.ymax -
    	redraw.visible_area.ymax + redraw.yscroll;
    if (war.xmin < bounds.xmax && war.xmax > bounds.xmin &&
    	war.ymin < bounds.ymax && war.ymax > bounds.ymin)
    {
      xcolourtrans_set_gcol((os_colour) ((options *) h)->temp_bg,
                            1<<8, 0, 0, 0);
      	/* Overwrite in foreground colour */
      xos_plot(4,	/* Move absolute */
      	bounds.xmin + redraw.visible_area.xmin - redraw.xscroll,
      	bounds.ymin + redraw.visible_area.ymax - redraw.yscroll);
      xos_plot(96 | 5,	/* Filled rectangle foreground absolute */
      	bounds.xmax - 1 + redraw.visible_area.xmin - redraw.xscroll,
      	bounds.ymax - 1 + redraw.visible_area.ymax - redraw.yscroll);
    }
    wimp_get_rectangle(&redraw, &more);
  }
  return true;
}

static bool hiding(int c, ToolboxEvent *e, IdBlock *id, options *opts)
{
  c=c; e=e;

  opts_release(opts, id->self_id);

  return true;
}

static bool showing(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  options *opts;

  c=c; e=e; h=h;

  E(toolbox_get_client_handle(0, id->self_id, (void **) &opts));

  write_window(opts, id->self_id);

  E(event_register_wimp_handler(id->self_id, Wimp_ERedrawWindow,
  	redraw_handler, opts));
  E(event_register_toolbox_handler(id->self_id, Window_HasBeenHidden,
  	(ToolboxEventHandler *) hiding, opts));

  return true;
}

void opts_release(options *opts, ObjectId win)
{
  E(event_deregister_wimp_handler(win, Wimp_ERedrawWindow,
  	redraw_handler, opts));
  E(event_deregister_toolbox_handler(win, Window_HasBeenHidden,
  	(ToolboxEventHandler *) hiding, opts));
}

static bool updated_stringset(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  c=c; e=e; h=h;

  update_mode(id->self_id);

  return true;
}

static bool choices_write_int(FILE *fp, const char *tag, int value)
{
  fprintf(fp, "%s:%d\n", tag, value);
  if (ferror(fp))
  {
    err_complain(0, msgs_qlookup("BadWOpts"));
    return false;
  }
  return true;
}

static bool choices_write_bool(FILE *fp, const char *tag, bool value)
{
  fprintf(fp, "%s:%s\n", tag, value ? "True" : "False");
  if (ferror(fp))
  {
    err_complain(0, msgs_qlookup("BadWOpts"));
    return false;
  }
  return true;
}

static bool choices_write_double(FILE *fp, const char *tag, double value)
{
  fprintf(fp, "%s:%f\n", tag, value);
  if (ferror(fp))
  {
    err_complain(0, msgs_qlookup("BadWOpts"));
    return false;
  }
  return true;
}

static bool choices_write_hex(FILE *fp, const char *tag,
                              unsigned long int value, int digits)
{
  char format[12];

  sprintf(format, "%%s:&%%%dx\n", digits);
  fprintf(fp, format, tag, value);
  if (ferror(fp))
  {
    err_complain(0, msgs_qlookup("BadWOpts"));
    return false;
  }
  return true;
}

static bool choices_save(FILE *fp, options *opts)
{
  if (!choices_write_int(fp, "ColourDepth", opts->colours))
    return false;
  if (!choices_write_int(fp, "DPI", opts->resolution))
    return false;
  if (!choices_write_hex(fp, "Background", opts->bg_colour >> 8, 6))
    return false;
  if (!choices_write_bool(fp, "Mask", opts->mask))
    return false;
  if (!choices_write_int(fp, "Quality", opts->quality))
    return false;
  if (!choices_write_int(fp, "Margin", opts->margin))
    return false;
  if (!choices_write_double(fp, "Rotation", opts->angle))
    return false;
  if (!choices_write_bool(fp, "ScaleToFit", opts->scale_to_fit))
    return false;
  switch (opts->scale_to_fit)
  {
    case true:
      if (!choices_write_int(fp, "ScaleX", opts->scale_fit_x))
        return false;
      if (!choices_write_int(fp, "ScaleY", opts->scale_fit_y))
        return false;
      break;
    case false:
      if (!choices_write_double(fp, "ScaleX", opts->scale_x))
        return false;
      if (!choices_write_double(fp, "ScaleY", opts->scale_y))
        return false;
      break;
  }
  if (!choices_write_bool(fp, "PreserveAspect", opts->keep_aspect))
    return false;
  return choices_write_int(fp, "Surplus", opts->aspect - gadget_EDGE);
}

static bool clicked_ok(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  options *opts;

  e=e; h=h;

  E(toolbox_get_client_handle(0, id->self_id, (void **) &opts));

  read_window(opts, id->self_id);

  if (c == event_OPTSSAVE)
  {
    bool saved;
    const char *filename;
    FILE *fp;

    filename = getenv("Choices$Write") ? choices_write : internal_choices;
    fp = fopen(filename, "w");
    if (!fp)
    {
      err_complain(0, msgs_qlookup("BadWOpts"));
      return true;
    }
    saved = choices_save(fp, opts);
    fclose(fp);
    if (saved)
      xosfile_set_type(filename, 0xfff);
    else
      xosfile_delete(filename, 0, 0, 0, 0, 0);
  }

  return true;
}

static bool clicked_cancel(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  options *opts;

  h=h;

  E(toolbox_get_client_handle(0, id->self_id, (void **) &opts));

  if (c == event_OPTSRELOAD)
    opts_reset(opts);

  if (e->hdr.flags & ActionButton_Selected_Adjust)
    write_window(opts, id->self_id);

  return true;
}

static bool selected_colour(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  options *opts;

  c=c; h=h;

  E(toolbox_get_client_handle(0, id->parent_id, (void **) &opts));

  reflect_transparency(id->parent_id, opts->temp_mask = e->hdr.flags & 1,
                       opts->temp_bg = e->data.words[0]);
  redraw_colour(id->parent_id);
  return true;
}

static bool opening_colour(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  options *opts;
  int cblk[2];

  c=c; e=e; h=h;

  E(toolbox_get_client_handle(0, id->parent_id, (void **) &opts));

  cblk[0] = (int) opts->temp_bg;
  cblk[1] = 0;
  E(colourdbox_set_colour(opts->temp_mask, id->self_id, cblk));

  return true;
}

static bool changeopts(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  c=c; e=e; h=h;

  update_fades(id->self_id);
  return true;
}

void opts_set_current_mode(options *opts)
{
  int modevars[3];
  int mvtags[] = {4, 5, 9, -1};

  xos_read_vdu_variables((os_vdu_var_list *) mvtags, modevars);
  opts->colours = modevars[2];
  switch (modevars[0])
  {
    case 1:	/* High resolution */
      switch (modevars[1])
      {
        case 2:
          opts->resolution = dpi_90x45;
          break;
        default:
          opts->resolution = dpi_90x90;
          break;
      }
      break;
    case 2:	/* Low resolution */
      opts->resolution = dpi_45x45;
      break;
  }
}

static void opts_reset_master(const char *filename)
{
  bool given = (filename != 0);
  bool result;

  /* Read choices from filename if given */
  if (filename)
  {
    if (opts_filetype(filename) == -1)
    {
      exit(EXIT_FAILURE);
    }
  }
  /* Try to open Choices file */
  if (!filename && getenv("Choices$Path"))
  {
    if (opts_filetype(filename = choices_load) == -1)
    {
      filename = 0;
    }
  }
  if (!filename)
  {
    if (opts_filetype(filename = internal_choices) == -1)
    {
      filename = 0;
    }
  }
  opts_set_current_mode(&default_opts);
  default_opts.bg_colour = 0xffffff00;
  default_opts.mask = false;
  default_opts.quality = quality_AntiAliased;
  default_opts.margin = DEFAULT_MARGIN;
  default_opts.angle = 0.0f;
  default_opts.scale_x = default_opts.scale_y = 1.0f;
  default_opts.scale_to_fit = false;
  default_opts.scale_fit_x = 640;
  default_opts.scale_fit_y = 480;
  default_opts.keep_aspect = true;
  default_opts.master = true;
  if (filename)
  {
    result = opts_load(filename);
    if (given && !result)
      exit(EXIT_FAILURE);
  }
}

static bool opts_modechange_handler(WimpMessage *m, void *h)
{
  m=m; h=h;

  opts_set_current_mode(&default_opts);
  opts_update_mode(&default_opts, opts_default_id);
  return false;
}

void opts_init(const char *filename)
{
  int osver;

  opts_reset_master(filename);

  if (err_is_task)
  {
    xosbyte1(129, 0, 0xff, &osver);
    risc_pc = (osver >= 0xa5);

    E(event_register_toolbox_handler(-1, event_SHOWOPTS, showing, 0));
    E(event_register_toolbox_handler(-1, StringSet_ValueChanged,
    	updated_stringset, 0));
    E(event_register_toolbox_handler(-1, event_OPTSOK, clicked_ok, 0));
    E(event_register_toolbox_handler(-1, event_OPTSSAVE, clicked_ok, 0));
    E(event_register_toolbox_handler(-1, event_OPTSRELOAD, clicked_cancel, 0));
    E(event_register_toolbox_handler(-1, event_OPTSCANCEL, clicked_cancel, 0));
    E(event_register_toolbox_handler(-1, ColourDbox_ColourSelected,
    	selected_colour, 0));
    E(event_register_toolbox_handler(-1, ColourDbox_AboutToBeShown,
    	opening_colour, 0));
    E(event_register_toolbox_handler(-1, event_CHANGEOPTS, changeopts, 0));
    E(event_register_message_handler(Wimp_MModeChange,
    	opts_modechange_handler, 0));
  }
}

void opts_reset(options *opts)
{
  if (opts->master)
    opts_reset_master(0);
  else
  {
    *opts = default_opts;
    opts->master = false;
  }
}
